package com.biz.primus.ms.base.service;

import com.biz.primus.base.redis.CrudRedisDao;
import com.biz.primus.base.redis.bean.BaseRedisObject;
import com.biz.primus.common.utils.JsonUtils;
import com.biz.primus.ms.base.jpa.po.BaseEntity;
import com.biz.primus.ms.base.jpa.po.BaseEntityPKIncrement;
import com.biz.primus.ms.base.jpa.po.BasePo;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.Assert;

import java.util.List;

/**
 * @author yanweijin
 * @date 2017/9/12
 */

@Slf4j
public abstract class AbstractBaseService implements ApplicationEventPublisherAware {

    @Autowired
    protected IdService idService;

    @Autowired
    protected TransactionOptDelayer delayer;

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 使用spring事件总线发布事件
     * 使用@TransactionalEventListener注解监听事件来达到"事务提交之后处理事件"的效果
     * 替代原本的四种事件发布方式
     *
     * @param event
     */
    protected void publishEvent(ApplicationEvent event) {
        this.applicationEventPublisher.publishEvent(event);
    }

    /**
     * 将PO持久化,并同步更新到redis
     * 对redis的修改将在事务提交后生效,如果事务失败,则不会生效
     * 返回PO转换成RO之后的对象
     *
     * @param repo     PO使用的JPA repository
     * @param redisDao RO使用的redis dao
     * @param transfer PO转换为RO的函数
     * @param po       要持久化的PO对象,只能是BaseEntity的子类
     * @return 从po转换为ro之后的ro对象
     * @author yanweijin
     * @date 2016年8月19日
     */
    protected <PO extends BaseEntity, RO extends BaseRedisObject<Long>> RO saveOrUpdateUsingPo(JpaRepository<PO,
            Long> repo, final CrudRedisDao<RO, Long> redisDao, PO po, Function<PO, RO> transfer) {
        Assert.notNull(po.getId(), "po必须要有id");
        if (log.isDebugEnabled()) {
            log.debug("save-po {}", po);
        }
        final RO ro = transfer.apply(po);
        if (log.isDebugEnabled()) {
            log.debug("transfer-po2ro, ro={}", ro);
        }

        //同步执行方案
        delayer.executeAfterTransactionCommit(() -> redisDao.save(ro));

        //异步执行方案,可能会有写了之后查不到的问题(因为查的是redis)
//		publishEvent(new RedisOptEvent(this,()->redisDao.save(ro)));
        repo.save(po);
        return ro;
    }

    protected <PO extends BaseEntity, RO extends BaseRedisObject<String>> RO saveOrUpdateUsingPoStr(JpaRepository<PO,
            Long> repo, final CrudRedisDao<RO, String> redisDao, PO po, Function<PO, RO> transfer) {
        Assert.notNull(po.getId(), "po必须要有id");
        if (log.isDebugEnabled()) {
            log.debug("save-po {}", po);
        }
        final RO ro = transfer.apply(po);
        if (log.isDebugEnabled()) {
            log.debug("transfer-po2ro, ro={}", ro);
        }

        //同步执行方案
        delayer.executeAfterTransactionCommit(() -> redisDao.save(ro));

        //异步执行方案,可能会有写了之后查不到的问题(因为查的是redis)
//		publishEvent(new RedisOptEvent(this,()->redisDao.save(ro)));
        repo.save(po);
        return ro;
    }

    protected <PO extends BaseEntity, RO extends BaseRedisObject> void saveOrUpdateUsingPos(
            CrudRepository<PO, Long> repo, final CrudRedisDao redisDao, final List<PO> pos,
            final Function<PO, RO> transfer) {
        for (PO po : pos) {
            Assert.notNull(po.getId(), "po必须要有id");
        }
        delayer.executeAfterTransactionCommit(() -> {List<RO> ros = Lists.transform(pos, transfer);
            if (log.isDebugEnabled()) {
                log.debug("POS - > ROS的结果为[{}]", JsonUtils.obj2Json(transfer));
            }
            for (RO ro : ros) {
                redisDao.save(ro);
            }
        });
        repo.save(pos);
    }

    /**
     * 用于保存主键自增的po,由于其id是由mysql生成,无法在事务提交之前拿到,因此本方法不再返回ro对象
     *
     * @param repo     po repository
     * @param redisDao ro redis dao
     * @param po       要持久化的po对象
     * @param transfer 将po转换为ro的函数
     * @author jun.liu
     */
    protected <PO extends BaseEntityPKIncrement, RO extends BaseRedisObject<Long>> void saveOrUpdateUsingPoPKIncreament(
            CrudRepository<PO, Long> repo, final CrudRedisDao<RO, Long> redisDao, final PO po,
            final Function<PO, RO> transfer) {
        if (log.isDebugEnabled()) {
            log.debug("save-po {}", po);
        }
	    delayer.executeAfterTransactionCommit(() -> {
            // 事务提交后,id被jpa写入原po
            final RO ro = transfer.apply(po);
            if (log.isDebugEnabled()) {
                log.debug("transfer-po2ro, ro={}", ro);
            }
            redisDao.save(ro);
        });
        repo.save(po);
    }

    /**
     * 批量用于保存主键自增的po,由于其id是由mysql生成,无法在事务提交之前拿到,因此本方法不再返回ro对象
     *
     * @param repo     po repository
     * @param redisDao ro redis dao
     * @param pos      要持久化的pos集合对象
     * @param transfer 将po转换为ro的函数
     * @author jun.liu
     */
    protected <PO extends BaseEntityPKIncrement, RO extends BaseRedisObject<Long>> void batchSaveOrUpdateUsingPoPKIncreament(
            CrudRepository<PO, Long> repo, final CrudRedisDao<RO, Long> redisDao, final List<PO> pos, final Function<PO, RO> transfer) {
        if (log.isDebugEnabled()) {
            log.debug("save-polist size={}", pos.size());
        }
	    delayer.executeAfterTransactionCommit(() -> {
            // 事务提交后,id被jpa写入原po
            for (PO po : pos) {
                final RO ro = transfer.apply(po);
                if (log.isDebugEnabled()) {
                    log.debug("transfer-po2ro, ro={}", ro);
                }
                redisDao.save(ro);
            }
        });
        repo.save(pos);
    }

    /**
     * 根据id删除实体,同时操作redis和mysql
     *
     * @param repo     PO使用的JPA repository
     * @param redisDao RO使用的redis dao
     * @param id       PO和RO的id
     * @author jun.liu
     */
    protected <PO extends BasePo, RO extends BaseRedisObject<Long>> void deleteEntity(CrudRepository<PO, Long> repo,
                                                                                      final CrudRedisDao<RO, Long> redisDao, Long id) {
        final RO ro = redisDao.findOne(id);
        repo.delete(id);
	    delayer.executeAfterTransactionCommit(() -> {
            redisDao.delete(ro);
        });
    }
}
